#卷积神经网络
import tensorflow as tf
from tensorflow.examples.tutorials.mnist import input_data

mnist = input_data.read_data_sets("/tmp/data/", one_hot=True)

#每个批次的大小
batch_size = 1000
#批次数
n_batch = int(mnist.train.num_examples/batch_size)

#初始化权值
def weight_variable(shape):
    init = tf.truncated_normal(shape=shape,stddev=0.1) #生成一个截断正态分布
    return tf.Variable(init)
#初始化偏置项
def bias_variable(shape):
    init = tf.constant(value=0.1, shape=shape)
    return tf.Variable(init)

#卷积层
def conv2d(x,W):
    '''
    :param x: tensor,4个维度,[batch(批次大小,比如100),in_height(采样的长),in_width(采样的宽),in_channels(通道数,黑白是1，彩色是3)]
    :param W: tensor 卷积核,[filter_height(卷积核长度),filter_width(卷积核宽度),in_channels(输入通道数),out_channels(输出通道数)]
    :param strides: 步长 strides[0] = strides[3] = 1(固定值) strides[1]表示X方向的步长，strides[2]表示Y方向上的步长
    :param: padding: 'SAME'(要补0的), 'VALID'(不补0)
    '''
    return tf.nn.conv2d(x,W,strides=[1,1,1,1],padding='SAME')

#池化层
def max_pool_2x2(x):
    '''
    :param x: 输入tensor
    :param ksize: 池化窗口大小,ksize[0]=ksize[3]=1(固定是1),ksize[1]行数 ,ksize[2]列数
    :param strides: 池化步长, strides[0] = strides[3] = 1(固定值) strides[1]表示X方向的步长，strides[2]表示Y方向上的步长
    :param padding: 'SAME'(要补0的), 'VALID'(不补0)
    '''
    return tf.nn.max_pool(x,ksize=[1,2,2,1],strides=[1,2,2,1],padding='SAME')

#定义两个placeholder
# 28*28 = 784 图片大小，转换为1维
x = tf.placeholder(dtype=tf.float32,shape=[None,784],name='input_x')
y = tf.placeholder(dtype=tf.float32,shape=[None,10],name='input_y')


#改变X的格式，转换4D向量，[batch(批次大小,比如100),in_height(采样的长),in_width(采样的宽),in_channels(通道数,黑白是1，彩色是3)]
x_image = tf.reshape(x,shape=[-1,28,28,1])

#初始化卷积层的权值和偏置项
#卷积核(采样窗口)大小5*5，1表示黑白通道，彩色是3，32表示输出通道数，32个卷积核从1维平面抽取特征，得到32个平面
W_conv1 = weight_variable(shape=[5,5,1,32])
#偏置项
#每一个卷积积一个偏置项，32个卷积核就是32个偏置项
b_conv1 = bias_variable(shape=[32])

#第一次卷积操作,把x_image和权值传入卷积层后加上偏置项，然后用relu进行激活函数
h_conv1 = conv2d(x_image,W_conv1)
#池化操作
h_pool_1 = max_pool_2x2(h_conv1)

#初始化第二个卷积层的权值和偏置项
#第二层卷积核5*5，第一层输出是32个平面，这里也用32个通道数接收，64个卷积核从32个平面抽取特征值
W_conv2 = weight_variable(shape=[5,5,32,64])
#第二层卷积，64个卷积核对应64个偏置项
b_conv2 = bias_variable(shape=[64])

#第二次卷积操作,由第一次卷积池化后的值与第二次权值，第二次偏置项构成
h_conv2 = tf.nn.relu(conv2d(h_pool_1, W_conv2) + b_conv2)
#第二次max_pooling
h_pool_2 = max_pool_2x2(h_conv2)

#28*28的图片第一次卷积后还是28*28，第一次池化后就是14*14
#第二次卷积后为14*14，第二次池化后变为了7*7
#经过上面操作后得到64张7*7的平面

#初始化第一个全连接层的权值
#上一层有7*7*64个神经元，全连接层有1024个神经元
W_fc1 = weight_variable(shape=[7*7*64,1024])
#1024个节点
b_fc1 = bias_variable([1024])

#把池化层2的输出层扁平化为1维
h_pool_2_flat = tf.reshape(h_pool_2, [-1, 7*7*64])
#求第一个全连接层的输出
h_fc1 = tf.nn.relu(tf.matmul(h_pool_2_flat, W_fc1) + b_fc1)

#keep_prob用来表示神经元的输出概率(保留率)
keep_prob = tf.placeholder(dtype=tf.float32)
h_fc1_drop = tf.nn.dropout(h_fc1, keep_prob)

#初始化第二个全连接层
#1024表示接收上一层的输出
W_fc2 = weight_variable([1024,10])
b_fc2 = bias_variable([10])
#softmax把输出值转化为概率
pred = tf.nn.softmax(tf.matmul(h_fc1_drop, W_fc2) + b_fc2)

#交叉商代价函数
loss = tf.reduce_mean(tf.nn.softmax_cross_entropy_with_logits(labels=y, logits=pred))
tf.summary.histogram("h-loss", loss)  # 可视化观看变量
tf.summary.scalar("s-loss", loss)
#优化器使用
train_step = tf.train.AdamOptimizer(1e-3).minimize(loss)
#结果存放在一个布尔列表中
#argmax 返回1维张量中最大值所在的下标位置
correct = tf.equal(tf.argmax(pred,1), tf.argmax(y,1))
#求准确率
accuracy = tf.reduce_mean(tf.cast(x=correct, dtype=tf.float32))
tf.summary.histogram("h-accr", accuracy)  # 可视化观看变量
tf.summary.scalar("s-accr", accuracy)

with tf.Session() as sess:
    sess.run(tf.global_variables_initializer())
    # 合并到Summary中
    merged = tf.summary.merge_all()
    # 选定可视化存储目录
    writer = tf.summary.FileWriter('logs/', sess.graph)
    for step in range(10):
        avg_cost = 0.
        for i in range(n_batch):
            batch_xs, batch_ys = mnist.train.next_batch(batch_size)
            sess.run(train_step, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.7})
            avg_cost_b = sess.run(loss, feed_dict={x: batch_xs, y: batch_ys, keep_prob: 0.7})
            avg_cost = avg_cost_b + avg_cost
        avg_cost = avg_cost / batch_size
        test_accr = sess.run(accuracy, feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
        print(step, 'accuracy:', test_accr, 'loss:', avg_cost)
        summary = sess.run(merged, feed_dict={x: mnist.test.images, y: mnist.test.labels, keep_prob: 1.0})
        writer.add_summary(summary, step)
print('end')